Java集合面试题
业务代码中集合使用的频率非常之高
我公司面试5K到25K的都会问,25K以上都是手写集合啦
版本:JDK1.8
集合大家庭
见集合面试题,我简单的总结下
1、List Set Map 的区别
2、ArrayList 和LinkedList 区别
4、HashMap 和 HashTable的区别
5、HashMap 和 ConcurrentHashMap 区别(数据结构、基本思想)
6、HashSet 和 HashMap 区别
7、HashSet 检查重复
8、hashCode() 和 equals()
9、集合的快速失败机制fail-fast
10、集合排序Comparable、Comparator
12、Collections工具、Collection接口(容易混淆)
1、List Set Map 的区别
List: 有序,可以多个元素引用相同的对象
Set: 无序,不重复,不可以多个元素引用相同对象
Map: 使用键值对存储,两个key可以引用相同的对象,但是key不能重复
我想说:HashSet的底层其实就是HashMap
jdk8的HashSet部分源码
public class HashSet<E>extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
2、ArrayList 和LinkedList 区别
ArrayList 和LinkedList 区别
ArrayList: 底层使用数组,存、读效率高;插入、删除特定位置效率低
LinkedList: 使用双向链表,插入、删除效率高
我想说:业务代码中你会发现到处都是ArrayList,极少看到有人用LinkedList,实际业务代码测试ArrayList插入删除其实也并不慢
3、Java集合的快速失败机制 “fail-fast”
代码经验不丰富的朋友,写业务代码时最容易犯的错误
记住2点
1.利用Iterator操作集合更新操作,remove、add2.一个异常 ConcurrentModificationException阿里代码规范也是这么规定的
正例:List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
4、为什么HashMap中String适合作为K
回答:内部已重写了equals()
、hashCode()
等方法,遵守了HashMap内部的规范,不容易出现Hash值计算错误的情况;
所以如果你要你的POJO类当HashMap的K,必须实现equals()、hashCode()方法
我无聊的点开放jdk源码,看了下包装类 Boolean 、Long、Byte、Double 都实现了 Comparable接口
jdk8中 源码都很费脑子,我就简单看下关键部分,面试问到有东西回复即可
String的equals()源码,有些面试官还真喜欢问
public boolean equals(Object anObject) {if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
5、集合排序Collections.sort的两种用法
接口
Comparable 有比较能力的
Comparator 比较器
Collections是工具类 Collection是接口,不要搞混淆了哦
注意String是实现了Camparable接口的,自带比较能力的
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence
业务代码一般都是通过Comparator内部类来实现排序
比较简单、方便
Collections.sort(intList,new Comparator<Integer>() {@Override
public int compare(Integer o1, Integer o2) {
// 返回值为int类型,大于0表示正序,小于0表示逆序
return o2-o1;
}
});
Comparable 需要修改pojo类比较麻烦
下面是jdk8,流式语法的排序,简洁到极致
List<Apple> inventory = Lists.newArrayList(new Apple(10, "red",DemoDate.string2Date("1990-12-31 23:59:00", DemoDate.DateFormatEnum.SIMPLE_FORMAT.getCode())),
new Apple(5, "red",DemoDate.string2Date("1990-12-31 23:59:00", DemoDate.DateFormatEnum.SIMPLE_FORMAT.getCode())),
new Apple(1, "green",DemoDate.string2Date("1991-12-31 23:59:00", DemoDate.DateFormatEnum.SIMPLE_FORMAT.getCode())),
new Apple(15, "green",DemoDate.string2Date("1992-12-31 23:59:00", DemoDate.DateFormatEnum.SIMPLE_FORMAT.getCode())),
new Apple(2, "red",DemoDate.string2Date("1980-12-31 23:59:00", DemoDate.DateFormatEnum.SIMPLE_FORMAT.getCode())));
System.out.println("=========华丽分割线====默认是升序=================");
//默认是升序
Collections.sort(inventory, Comparator.comparing(Apple::getBirthday));
inventory.forEach(System.out::println);
System.out.println("=========华丽分割线====默认是降序=================");
//默认是升序
Collections.sort(inventory, Comparator.comparing(Apple::getBirthday).reversed());
inventory.forEach(System.out::println);
System.out.println("=========华丽分割线=====================");
System.out.println("=========华丽分割线=====================");
//先根据生日排序(倒叙),然后相同生日就根据重量排序
Collections.sort(inventory, Comparator.comparing(Apple::getBirthday)
.reversed()
.thenComparing(Apple::getWeight));
5、面试有时候会问到K/V可以为null(多么老残的问题)
特别注意下HashMap的K/V允许null
6、HashMap的具体实现知道吗?
能把数据结构、主要的特性说出来面试也加分的。
源码我也记不住,真的太复杂了
注意把jdk版本说清楚 1.7和1.8是不一样的哦
说清楚1.8也可以
HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。
上图中,每个绿色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
loadFactor:负载因子,默认为 0.75。
threshold:扩容的阈值,等于 capacity * loadFactor
HashMap是基于 哈希表 的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null 值 和 null 键。HashMap 存储的是键值对,HashMap很快,但是不保证映射的顺序,特别是它不保证改顺序不变;
HashMap 实际上是一个“链表散列”的数据结构,即 数组 和 链表 的结合体。
数组: 存储区间连续,占用内存严重,寻址容易,插入删除困难;
链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易;
基本原理:
声明一个下标范围比较大的数组来存储元素;
设计一个哈希函数来获取每一个元素的Key(关键字)的函数值(数组下标, hash值)相对应
数组存储的元素的一个Entry类,这个类有三个数据域,key、value、next(指向下一个Entry)
Java8 HashMap
数组+链表+红黑色
翻译成代码: table + Node + TreeNode
Java8 中使用 Node(链表),核心属性 key,value,hash 和 next ,Node 只能用于链表的情况,
红黑树的情况需要使用 TreeNode。
我们根据数组元素中,第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树的。